#include "test_types.h"
#include "nosal/core/result.h"

#include "gtest/gtest.h"
#include <type_traits>

using namespace netaos;

// A trivial type we're using as 'E' type for most Result usage
struct Error final
{ };

// Change this to compare the static type properties of nosal::core::Result against those of std::optional.
// These properties should be mostly the same, as both constitute "wrapper" types that need to propagate certain
// properties of the wrapped type onto the wrapper type itself, most importantly the copyable/moveable property.

#if 0
static_assert(__cplusplus >= 201703L, "need C++17 conformance for compiling this part");
#    include <optional>
#    define ISSTD
template <typename T>
using Type = std::optional<T>;
#else
template <typename T>
using Type = nosal::core::Result<T, Error>;
#endif

// ----------------------------------------------------------------

TEST(PropertiesTest, int_Properties)
{
    using T = int;
    using U = Type<T>;

    EXPECT_TRUE(std::is_destructible<U>::value);
    EXPECT_TRUE(std::is_copy_constructible<U>::value);
    EXPECT_TRUE(std::is_move_constructible<U>::value);
    EXPECT_TRUE(std::is_copy_assignable<U>::value);
    EXPECT_TRUE(std::is_move_assignable<U>::value);

    EXPECT_TRUE((std::is_constructible<U, T const&>::value));
    EXPECT_TRUE((std::is_constructible<U, T&&>::value));
    EXPECT_TRUE((std::is_assignable<U, T const&>::value));
    EXPECT_TRUE((std::is_assignable<U, T&&>::value));
    EXPECT_TRUE((std::is_convertible<T, U const&>::value));
    EXPECT_TRUE((std::is_convertible<T, U&&>::value));

    EXPECT_TRUE(std::is_trivially_destructible<U>::value);
    // Not yet possible
    //    EXPECT_TRUE(std::is_trivially_copyable<U>::value);
    //    EXPECT_TRUE(std::is_trivially_copy_constructible<U>::value);
    //    EXPECT_TRUE(std::is_trivially_move_constructible<U>::value);
    //    EXPECT_TRUE(std::is_trivially_copy_assignable<U>::value);
    //    EXPECT_TRUE(std::is_trivially_move_assignable<U>::value);
    EXPECT_FALSE((std::is_trivially_assignable<U, T const&>::value));
    EXPECT_FALSE((std::is_trivially_assignable<U, T&&>::value));
    EXPECT_TRUE(std::is_literal_type<U>::value);
    EXPECT_TRUE(std::is_standard_layout<U>::value);  // but probably not guaranteed by the standard
    EXPECT_FALSE(std::is_trivial<U>::value);
}

TEST(PropertiesTest, Trivial_Properties)
{
    using T = Trivial;
    using U = Type<T>;

    EXPECT_TRUE(std::is_destructible<U>::value);
    EXPECT_TRUE(std::is_copy_constructible<U>::value);
    EXPECT_TRUE(std::is_move_constructible<U>::value);
    EXPECT_TRUE(std::is_copy_assignable<U>::value);
    EXPECT_TRUE(std::is_move_assignable<U>::value);

    EXPECT_TRUE((std::is_constructible<U, T const&>::value));
    EXPECT_TRUE((std::is_constructible<U, T&&>::value));
    EXPECT_TRUE((std::is_assignable<U, T const&>::value));
    EXPECT_TRUE((std::is_assignable<U, T&&>::value));
    EXPECT_TRUE((std::is_convertible<T, U const&>::value));
    EXPECT_TRUE((std::is_convertible<T, U&&>::value));

    EXPECT_TRUE(std::is_trivially_destructible<U>::value);
    // Not yet possible
    //    EXPECT_TRUE(std::is_trivially_copyable<U>::value);
    //    EXPECT_TRUE(std::is_trivially_copy_constructible<U>::value);
    //    EXPECT_TRUE(std::is_trivially_move_constructible<U>::value);
    //    EXPECT_TRUE(std::is_trivially_copy_assignable<U>::value);
    //    EXPECT_TRUE(std::is_trivially_move_assignable<U>::value);
    EXPECT_FALSE((std::is_trivially_assignable<U, T const&>::value));
    EXPECT_FALSE((std::is_trivially_assignable<U, T&&>::value));
    EXPECT_TRUE(std::is_literal_type<U>::value);
    EXPECT_TRUE(std::is_standard_layout<U>::value);  // but probably not guaranteed by the standard
    EXPECT_FALSE(std::is_trivial<U>::value);  // because it has no trivial default constructor
}

TEST(PropertiesTest, Literal_Properties)
{
    using T = Literal;
    using U = Type<T>;

    EXPECT_TRUE(std::is_destructible<U>::value);
    EXPECT_TRUE(std::is_copy_constructible<U>::value);
    EXPECT_TRUE(std::is_move_constructible<U>::value);
    EXPECT_TRUE(std::is_copy_assignable<U>::value);
    EXPECT_TRUE(std::is_move_assignable<U>::value);

    EXPECT_TRUE((std::is_constructible<U, T const&>::value));
    EXPECT_TRUE((std::is_constructible<U, T&&>::value));
    EXPECT_TRUE((std::is_assignable<U, T const&>::value));
    EXPECT_TRUE((std::is_assignable<U, T&&>::value));
    EXPECT_TRUE((std::is_convertible<T, U const&>::value));
    EXPECT_TRUE((std::is_convertible<T, U&&>::value));

    EXPECT_TRUE(std::is_trivially_destructible<U>::value);
    // Not yet possible
    //    EXPECT_TRUE(std::is_trivially_copyable<U>::value);
    //    EXPECT_TRUE(std::is_trivially_copy_constructible<U>::value);
    //    EXPECT_TRUE(std::is_trivially_move_constructible<U>::value);
    //    EXPECT_TRUE(std::is_trivially_copy_assignable<U>::value);
    //    EXPECT_TRUE(std::is_trivially_move_assignable<U>::value);
    EXPECT_FALSE((std::is_trivially_assignable<U, T const&>::value));
    EXPECT_FALSE((std::is_trivially_assignable<U, T&&>::value));
    EXPECT_TRUE(std::is_literal_type<U>::value);
    EXPECT_TRUE(std::is_standard_layout<U>::value);  // but probably not guaranteed by the standard
    EXPECT_FALSE(std::is_trivial<U>::value);
}

#ifdef ISSTD
TEST(PropertiesTest, LiteralNonCopyable_Properties)
{
    using T = LiteralNonCopyable;
    using U = Type<T>;

    EXPECT_TRUE(std::is_destructible<U>::value);
    EXPECT_FALSE(std::is_copy_constructible<U>::value);
    EXPECT_TRUE(std::is_move_constructible<U>::value);
    EXPECT_FALSE(std::is_copy_assignable<U>::value);
    EXPECT_TRUE(std::is_move_assignable<U>::value);

#    ifdef ISSTD
    EXPECT_FALSE((std::is_constructible<U, T const&>::value));  // missing SFINAE for converting ctor
    EXPECT_FALSE(
        (std::is_assignable<U, T const&>::value));  // because it's move-assigning from implicitly created temporary
#    endif
    EXPECT_TRUE((std::is_constructible<U, T&&>::value));
    EXPECT_TRUE((std::is_assignable<U, T&&>::value));
    EXPECT_TRUE((std::is_convertible<T, U const&>::value));
    EXPECT_TRUE((std::is_convertible<T, U&&>::value));

    EXPECT_TRUE(std::is_trivially_destructible<U>::value);
    EXPECT_FALSE(std::is_trivially_copyable<U>::value);
    EXPECT_FALSE(std::is_trivially_copy_constructible<U>::value);
    EXPECT_FALSE(std::is_trivially_move_constructible<U>::value);
    EXPECT_FALSE(std::is_trivially_copy_assignable<U>::value);
    EXPECT_FALSE(std::is_trivially_move_assignable<U>::value);
    EXPECT_FALSE((std::is_trivially_assignable<U, T const&>::value));
    EXPECT_FALSE((std::is_trivially_assignable<U, T&&>::value));
    EXPECT_TRUE(std::is_literal_type<U>::value);
    EXPECT_TRUE(std::is_standard_layout<U>::value);  // but probably not guaranteed by the standard
    EXPECT_FALSE(std::is_trivial<U>::value);
}
#endif

TEST(PropertiesTest, NonLiteral_Properties)
{
    using T = NonLiteral;
    using U = Type<T>;

    EXPECT_TRUE(std::is_destructible<U>::value);
    EXPECT_TRUE(std::is_copy_constructible<U>::value);
    EXPECT_TRUE(std::is_move_constructible<U>::value);
    EXPECT_TRUE(std::is_copy_assignable<U>::value);
    EXPECT_TRUE(std::is_move_assignable<U>::value);

    EXPECT_TRUE((std::is_constructible<U, T const&>::value));
    EXPECT_TRUE((std::is_constructible<U, T&&>::value));
    EXPECT_TRUE((std::is_assignable<U, T const&>::value));
    EXPECT_TRUE((std::is_assignable<U, T&&>::value));
    EXPECT_TRUE((std::is_convertible<T, U const&>::value));
    EXPECT_TRUE((std::is_convertible<T, U&&>::value));

    EXPECT_FALSE(std::is_trivially_destructible<U>::value);
    EXPECT_FALSE(std::is_trivially_copyable<U>::value);
    EXPECT_FALSE(std::is_trivially_copy_constructible<U>::value);
    EXPECT_FALSE(std::is_trivially_move_constructible<U>::value);
    EXPECT_FALSE(std::is_trivially_copy_assignable<U>::value);
    EXPECT_FALSE(std::is_trivially_move_assignable<U>::value);
    EXPECT_FALSE((std::is_trivially_assignable<U, T const&>::value));
    EXPECT_FALSE((std::is_trivially_assignable<U, T&&>::value));
    EXPECT_FALSE(std::is_literal_type<U>::value);
    EXPECT_TRUE(std::is_standard_layout<U>::value);  // but probably not guaranteed by the standard
    EXPECT_FALSE(std::is_trivial<U>::value);
}

#ifdef ISSTD
TEST(PropertiesTest, NonCopyable_Properties)
{
    using T = NonCopyable;
    using U = Type<T>;

    EXPECT_TRUE(std::is_destructible<U>::value);
    EXPECT_TRUE(!std::is_copy_constructible<U>::value);
    EXPECT_TRUE(std::is_move_constructible<U>::value);
    EXPECT_TRUE(!std::is_copy_assignable<U>::value);
    EXPECT_TRUE(std::is_move_assignable<U>::value);

#    ifdef ISSTD
    EXPECT_FALSE((std::is_constructible<U, T const&>::value));  // missing SFINAE for converting ctor
    EXPECT_FALSE(
        (std::is_assignable<U, T const&>::value));  // because it's move-assigning from implicitly created temporary
#    endif
    EXPECT_TRUE((std::is_constructible<U, T&&>::value));
    EXPECT_TRUE((std::is_assignable<U, T&&>::value));
    EXPECT_TRUE((std::is_convertible<T, U const&>::value));
    EXPECT_TRUE((std::is_convertible<T, U&&>::value));

    EXPECT_FALSE(std::is_trivially_destructible<U>::value);
    EXPECT_FALSE(std::is_trivially_copyable<U>::value);
    EXPECT_FALSE(std::is_trivially_copy_constructible<U>::value);
    EXPECT_FALSE(std::is_trivially_move_constructible<U>::value);
    EXPECT_FALSE(std::is_trivially_copy_assignable<U>::value);
    EXPECT_FALSE(std::is_trivially_move_assignable<U>::value);
    EXPECT_FALSE((std::is_trivially_assignable<U, T const&>::value));
    EXPECT_FALSE((std::is_trivially_assignable<U, T&&>::value));
    EXPECT_FALSE(std::is_literal_type<U>::value);
    EXPECT_TRUE(std::is_standard_layout<U>::value);  // but probably not guaranteed by the standard
    EXPECT_FALSE(std::is_trivial<U>::value);
}

TEST(PropertiesTest, NonMoveable_Properties)
{
    using T = NonMoveable;
    using U = Type<T>;

    EXPECT_TRUE(std::is_destructible<U>::value);
    EXPECT_TRUE(!std::is_copy_constructible<U>::value);
    EXPECT_TRUE(!std::is_move_constructible<U>::value);
    EXPECT_TRUE(!std::is_copy_assignable<U>::value);
    EXPECT_TRUE(!std::is_move_assignable<U>::value);

#    ifdef ISSTD
    EXPECT_FALSE((std::is_constructible<U, T const&>::value));  // missing SFINAE for converting ctor
    EXPECT_FALSE((std::is_constructible<U, T&&>::value));  // missing SFINAE for converting ctor
    EXPECT_FALSE((std::is_convertible<T, U const&>::value));
    EXPECT_FALSE((std::is_convertible<T, U&&>::value));
#    endif
    EXPECT_FALSE((std::is_assignable<U, T const&>::value));
    EXPECT_FALSE((std::is_assignable<U, T&&>::value));

    EXPECT_FALSE(std::is_trivially_destructible<U>::value);
    EXPECT_FALSE(std::is_trivially_copyable<U>::value);
    EXPECT_FALSE(std::is_trivially_copy_constructible<U>::value);
    EXPECT_FALSE(std::is_trivially_move_constructible<U>::value);
    EXPECT_FALSE(std::is_trivially_copy_assignable<U>::value);
    EXPECT_FALSE(std::is_trivially_move_assignable<U>::value);
    EXPECT_FALSE((std::is_trivially_assignable<U, T const&>::value));
    EXPECT_FALSE((std::is_trivially_assignable<U, T&&>::value));
    EXPECT_FALSE(std::is_literal_type<U>::value);
    EXPECT_TRUE(std::is_standard_layout<U>::value);  // but probably not guaranteed by the standard
    EXPECT_FALSE(std::is_trivial<U>::value);
}

TEST(PropertiesTest, CopyableButNotMoveable_Properties)
{
    using T = CopyableButNotMoveable;
    using U = Type<T>;

    EXPECT_TRUE(std::is_destructible<U>::value);
    EXPECT_TRUE(std::is_copy_constructible<U>::value);
    EXPECT_TRUE(std::is_move_constructible<U>::value);  // move ctor does not participate, but falls back to copy ctor
    EXPECT_TRUE(std::is_copy_assignable<U>::value);
    EXPECT_TRUE(std::is_move_assignable<U>::value);  // move op does not participate, but falls back to copy op

#    ifdef ISSTD
    EXPECT_FALSE((std::is_constructible<U, T&&>::value));
    EXPECT_FALSE((std::is_assignable<U, T&&>::value));
    EXPECT_FALSE((std::is_convertible<T, U const&>::value));
    EXPECT_FALSE((std::is_convertible<T, U&&>::value));
#    endif
    EXPECT_TRUE((std::is_constructible<U, T const&>::value));
    EXPECT_TRUE((std::is_assignable<U, T const&>::value));

    EXPECT_FALSE(std::is_trivially_destructible<U>::value);
    EXPECT_FALSE(std::is_trivially_copyable<U>::value);
    EXPECT_FALSE(std::is_trivially_copy_constructible<U>::value);
    EXPECT_FALSE(std::is_trivially_move_constructible<U>::value);
    EXPECT_FALSE(std::is_trivially_copy_assignable<U>::value);
    EXPECT_FALSE(std::is_trivially_move_assignable<U>::value);
    EXPECT_FALSE((std::is_trivially_assignable<U, T const&>::value));
    EXPECT_FALSE((std::is_trivially_assignable<U, T&&>::value));
    EXPECT_FALSE(std::is_literal_type<U>::value);
    EXPECT_TRUE(std::is_standard_layout<U>::value);  // but probably not guaranteed by the standard
    EXPECT_FALSE(std::is_trivial<U>::value);
}

TEST(PropertiesTest, NonCopyAssignable_Properties)
{
    using T = NonCopyAssignable;
    using U = Type<T>;

    EXPECT_TRUE(std::is_destructible<U>::value);
    EXPECT_TRUE(std::is_copy_constructible<U>::value);
    EXPECT_TRUE(std::is_move_constructible<U>::value);
    EXPECT_FALSE(std::is_copy_assignable<U>::value);
    EXPECT_TRUE(std::is_move_assignable<U>::value);

    EXPECT_TRUE((std::is_constructible<U, T const&>::value));
    EXPECT_TRUE((std::is_constructible<U, T&&>::value));
    EXPECT_TRUE((std::is_assignable<U, T const&>::value));
    EXPECT_TRUE((std::is_assignable<U, T&&>::value));
    EXPECT_TRUE((std::is_convertible<T, U const&>::value));
    EXPECT_TRUE((std::is_convertible<T, U&&>::value));

    EXPECT_FALSE(std::is_trivially_destructible<U>::value);
    EXPECT_FALSE(std::is_trivially_copyable<U>::value);
    EXPECT_FALSE(std::is_trivially_copy_constructible<U>::value);
    EXPECT_FALSE(std::is_trivially_move_constructible<U>::value);
    EXPECT_FALSE(std::is_trivially_copy_assignable<U>::value);
    EXPECT_FALSE(std::is_trivially_move_assignable<U>::value);
    EXPECT_FALSE((std::is_trivially_assignable<U, T const&>::value));
    EXPECT_FALSE((std::is_trivially_assignable<U, T&&>::value));
    EXPECT_FALSE(std::is_literal_type<U>::value);
    EXPECT_TRUE(std::is_standard_layout<U>::value);  // but probably not guaranteed by the standard
    EXPECT_FALSE(std::is_trivial<U>::value);
}

TEST(PropertiesTest, NonMoveAssignable_Properties)
{
    using T = NonMoveAssignable;
    using U = Type<T>;

    EXPECT_TRUE(std::is_destructible<U>::value);
    EXPECT_FALSE(std::is_copy_constructible<U>::value);
    EXPECT_TRUE(std::is_move_constructible<U>::value);
    EXPECT_FALSE(std::is_copy_assignable<U>::value);
    EXPECT_FALSE(std::is_move_assignable<U>::value);

#    ifdef ISSTD
    EXPECT_FALSE((std::is_constructible<U, T const&>::value));  // missing SFINAE for converting ctor
#    endif
    EXPECT_TRUE((std::is_constructible<U, T&&>::value));
    EXPECT_FALSE((std::is_assignable<U, T const&>::value));
    EXPECT_FALSE((std::is_assignable<U, T&&>::value));
    EXPECT_TRUE((std::is_convertible<T, U const&>::value));
    EXPECT_TRUE((std::is_convertible<T, U&&>::value));

    EXPECT_FALSE(std::is_trivially_destructible<U>::value);
    EXPECT_FALSE(std::is_trivially_copyable<U>::value);
    EXPECT_FALSE(std::is_trivially_copy_constructible<U>::value);
    EXPECT_FALSE(std::is_trivially_move_constructible<U>::value);
    EXPECT_FALSE(std::is_trivially_copy_assignable<U>::value);
    EXPECT_FALSE(std::is_trivially_move_assignable<U>::value);
    EXPECT_FALSE((std::is_trivially_assignable<U, T const&>::value));
    EXPECT_FALSE((std::is_trivially_assignable<U, T&&>::value));
    EXPECT_FALSE(std::is_literal_type<U>::value);
    EXPECT_TRUE(std::is_standard_layout<U>::value);  // but probably not guaranteed by the standard
    EXPECT_FALSE(std::is_trivial<U>::value);
}
#endif

TEST(PropertiesTest, NotNothrowMoveConstructible_Properties)
{
    using T = NotNothrowMoveConstructible;
    using U = Type<T>;

    EXPECT_TRUE(std::is_destructible<U>::value);
    EXPECT_TRUE(std::is_copy_constructible<U>::value);
    EXPECT_TRUE(std::is_move_constructible<U>::value);
    EXPECT_TRUE(std::is_copy_assignable<U>::value);
    EXPECT_TRUE(std::is_move_assignable<U>::value);

    EXPECT_TRUE((std::is_constructible<U, T const&>::value));
    EXPECT_TRUE((std::is_constructible<U, T&&>::value));
    EXPECT_TRUE((std::is_assignable<U, T const&>::value));
    EXPECT_TRUE((std::is_assignable<U, T&&>::value));
    EXPECT_TRUE((std::is_convertible<T, U const&>::value));
    EXPECT_TRUE((std::is_convertible<T, U&&>::value));

    EXPECT_FALSE(std::is_nothrow_copy_constructible<U>::value);
    EXPECT_FALSE(std::is_nothrow_move_constructible<U>::value);
    EXPECT_FALSE(std::is_nothrow_copy_assignable<U>::value);
    EXPECT_FALSE(std::is_nothrow_move_assignable<U>::value);

    EXPECT_FALSE(std::is_trivially_destructible<U>::value);
    EXPECT_FALSE(std::is_trivially_copyable<U>::value);
    EXPECT_FALSE(std::is_trivially_copy_constructible<U>::value);
    EXPECT_FALSE(std::is_trivially_move_constructible<U>::value);
    EXPECT_FALSE(std::is_trivially_copy_assignable<U>::value);
    EXPECT_FALSE(std::is_trivially_move_assignable<U>::value);
    EXPECT_FALSE((std::is_trivially_assignable<U, T const&>::value));
    EXPECT_FALSE((std::is_trivially_assignable<U, T&&>::value));
    EXPECT_FALSE(std::is_literal_type<U>::value);
    EXPECT_TRUE(std::is_standard_layout<U>::value);  // but probably not guaranteed by the standard
    EXPECT_FALSE(std::is_trivial<U>::value);
}
